# Authors: Damian 'Rush' Kaczmarek (main), Michal 'mucha' Muszynski (!seen, !last, !stats $nick)
# License: MIT

# TODO - When, more help system

package History;
use base Module;
use strict;
use integer;
use DBD::SQLite;
use POSIX qw(strftime);
use Time::Local qw(timelocal timelocal_nocheck);

use Data::Dumper;
use Core::Util;


Module::define_command("history", \&on_history, "count|c=i", "author|a=s@", "exact-author|e", "ignore-case|i", "reverse|r", "from|f=s", "to|t=s", "when|w=s",
	{
		usage => "<options> <regexp> ... <other_regexps>",
		about => "Search channel history for messages using regular expressions",
		count => "Number of results to print out. (max 5)",
		'reverse' => "Search backwards",
		'exact-author' => "Match the author without regular expressions",
		author => "Search for a particular author. --fuzzy will make the match not exact",
		when => "",
		help_when => "",
		from => "Search from particular time. Can serve as additional constraint to --when",
		help_from => "",
		to => "Search to particular time. Can serve as additional constraint to --when",
		help_to => "",
		'ignore-case' => "Ignore case in regular expression matches and author matches",
		example => "-c 3 -a Nowaker '(hamburger|pizza)' 'now'",
	}
);

Module::define_command("last", \&on_last, 
	{
		usage => "<nick>",
		about => "Find last message of given nick",
		example => "Martin",
	}
);
Module::define_command("seen", \&on_seen,
	{
		usage => "<nick>",
		about => "Find last quit/leave event of given nick. Useful for finding out why your session died",
		example => "Martin",
	}
);
Module::define_command("stats", \&on_stats,
	{
		usage => "<optional_nick>",
		about => "Display statistics for channel",
		example => "Martin",
	}
);


my $dbh = DBI->connect("dbi:SQLite:History.db") || die "Cannot open database: $DBI::errstr";

$dbh->do(
	"CREATE TABLE IF NOT EXISTS history (id INTEGER PRIMARY KEY, timestamp INTEGER, type, channel, author, target, text)"
);
$dbh->do("CREATE TABLE IF NOT EXISTS stats (channel, author, size INTEGER)");

my $ignore_case = 0;

$dbh->func(
	'regexp', 2,
	sub {
		my ($regex, $string) = @_;
		if ($string =~ /^!history /) {
			return 0;
		}
		if($ignore_case) {
			return $string =~ /$regex/i;
		}
		else {
			return $string =~ /$regex/;
		}
	},
	'create_function'
);

sub read_timestamp
{
	my $date = $_[0];

	#below accepts the following data formats
	# [%d/%m/%y]
	# [%d/%m/%y][%h]
	# [%d/%m/%y][%h:%m]
	# [%d/%m/%y][%h:%m:%s]
	# %d - day, may be 1-2 digits
	# %m - month, may be 1-2 digits
	# %y - year, may be 1-4 digits(1-2 digit are current century, 3 digits means from 1900)
	# %h - hour, 1-2 digits
	# %m - minutes, 1-2 digits
	# %s - seconds, 1-2 digits
	$date =~ /\[(\d{1,2})\/(\d{1,2})\/(\d{1,4})\](\[(\d{1,2})(:(\d{1,2})(:(\d{1,2}))?\])?)?/;
	my ($sec, $min, $hours, $mday, $mon, $year) = ($9, $7, $5, $1, $2, $3);

	return timelocal_nocheck($sec, $min, $hours, $mday, $mon - 1, $year);
}


sub timestamp
{
	return strftime("[%d/%m/%y][%R]", localtime($_[0]));
}

sub putdata
{
	$dbh->do(   "INSERT INTO history (timestamp, type, channel, author, target, text) VALUES("
		  . $dbh->quote($_[0]) . ", "
		  . $dbh->quote($_[1]) . ", '"
		  . $Core::channel . "', "
		  . $dbh->quote($_[2]) . ", "
		  . $dbh->quote($_[3]) . ", "
		  . $dbh->quote($_[4])
		  . ")");
}

sub get_length
{
	my $bytes = $_[0];
	my $out;
	if ($bytes < 10240) {
		return $bytes . "B";
	}
	else {
		return $bytes / 1024 . "kB";
	}
}

sub on_sendme
{
	putdata(time(), 'me', $_[0], '', $_[1]);
}

sub on_sendmsg
{
	putdata(time(), 'public', $_[0], '', $_[1]);
}

sub on_me
{
	putdata(time(), 'me', $_[0], '', $_[1]);
}

sub on_kick
{
	putdata(time(), 'kick', $_[0], $_[1], $_[2]);
}

sub on_part
{
	putdata(time(), 'part', $_[0], '', $_[1]);
}

sub on_quit
{
	putdata(time(), 'quit', $_[0], '', $_[1]);
}

sub on_join
{
	putdata(time(), 'join', $_[0], '', $_[1]."@".$_[2]);
}

sub on_public
{
	putdata(time(), 'public', $_[0], '', $_[1]);
}

sub parse_when {
	my ($sec, $min, $hour, $mday, $mon, $year) = localtime();

	if($_[0] =~ /today/) {
		return (timelocal(0, 0, 0, $mday, $mon , $year), time());
	}
	elsif($_[0] =~ /yesterday/) {
		print "dupa\n";
		return (timelocal(0, 0, 0, $mday-1, $mon, $year), timelocal(0, 0, 0, $mday, $mon - 1, $year));
	}
	elsif($_[0] =~ /lastmonth/) {
		return (timelocal(0, 0, 0, $mday, $mon - 1, $year), timelocal(0, 0, 0, 1, $mon, $year));
	}
}

sub on_history {
	my ($nick, $options, @args) = @_;

	if(!@args) { # if not provided any regexp, assume matching everything
		push(@args, ".*");
	}


	my $rev;
	my ($author_sql, @author_args);
	my $limit = 3;
	my ($timeframe_sql, $from, $to);
	$from = 1;
	$to = time();

	$rev = ' DESC';
	if($options->{'reverse'}) {
		$rev = '' 
	}

	if($options->{'ignore-case'}) {
		$ignore_case = 1;
	}
	else {
		$ignore_case = 0;
	}

	if($options->{author}) {
		my @authors = @{$options->{author}};
		@author_args = @authors;
		$author_sql = "AND ";
		if(!$options->{'exact-author'}) {
			$author_sql .= " (" . join(" OR ", map { "author REGEXP (?)"} @authors) . ") ";
		}
		else {
			if($ignore_case) {
				$author_sql .= " (" . join(" OR ", map {"upper(author) = upper((?))" } @authors) . ") ";
			}
			else {
				$author_sql .= " (" . join(" OR ", map {"author = (?)" } @authors) . ") ";
			}
		}
	}

	if($options->{count}) {
		my $limit = $options->{count};
		if($limit < 0) {
			$limit = 3;
		}
		else {
			$limit = 7;
		}
	}

	if($options->{when}) {
		($from, $to) = parse_when($options->{when});
	}

	if($options->{from}) {
		$from = read_timestamp($options->{from});
	}
	if($options->{to}) {
		$to = read_timestamp($options->{to});
	}

	if ($from && $to) {
		$timeframe_sql = "AND (timestamp BETWEEN $from AND $to) ";
	}

	my $sql = "SELECT timestamp,type,author,text FROM history WHERE author != '"
		  . $Core::nickname . "' "
		  . $timeframe_sql
		  . " AND (type = 'public' OR type = 'me') AND (" .  join(" AND ", map {"text REGEXP (?)"} @args)   . ") "
		  . $author_sql
		  . " ORDER BY timestamp "
		  . $rev
		  . " LIMIT "
		  . $limit;

#	print $sql . "\n";


	my $sel =$dbh->prepare($sql);

	my @res;

	eval {@res = $dbh->selectall_arrayref($sel, undef, @args, @author_args);};

	if (length($rev) > 0) {    # we want to print logs in natural order, always
		@{$res[0]} = reverse @{$res[0]};
	}
	foreach my $match (@{$res[0]}) {
		if (@{$match}[1] eq 'public') {
			Core::send_msg(timestamp(@{$match}[0]) . " <" . Util::color_nick(@{$match}[2]) . "> " . @{$match}[3]);
		}
		else {
			Core::send_msg(timestamp(@{$match}[0]) . " * " . @{$match}[2] . " " . @{$match}[3]);
		}
	}
}

sub on_seen {
	my ($nick, $options, @args) = @_;
	return if(!@args);
	
	my @res =
	  $dbh->selectall_arrayref("SELECT timestamp,type,author,text FROM history WHERE author = (?)" 
		  . " AND (type = 'part' OR type = 'quit') ORDER BY timestamp DESC LIMIT 1", undef, $args[0]);
	my $match = shift(@{$res[0]});

	if (length(@{$match}[2]) > 0) {
		Core::send_msg("["
			  . Util::howlongago(@{$match}[0])
			  . "] -!- "
			  . @{$match}[2]
			  . " has quit ["
			  . @{$match}[3]
			  . "]");
	}
	else {

	}
}

sub on_last {
	my ($nick, $options, @args) = @_;
	return if(!@args);

	my @res =
	  $dbh->selectall_arrayref("SELECT timestamp,type,author,text FROM history WHERE author = (?)" 
		  . " AND (type = 'public' OR type = 'me') ORDER BY timestamp DESC LIMIT 1", undef, $args[0]);
	my $match = shift(@{$res[0]});
	if (length(@{$match}[2]) > 0) {
		Core::send_msg("["
			  . Util::howlongago(@{$match}[0]) . "] <"
			  . @{$match}[2] . "> "
			  . @{$match}[3]);
	}
	else {
		Core::send_msg("Nick " . '"' . $1 . '" not in database.');
	}
}

sub on_stats
{
	my ($nick, $options, @args) = @_;
	my $text = $args[0];

	if (length($text) < 1) {
		my @res =
		  $dbh->selectall_arrayref("SELECT COUNT(*),SUM(LENGTH(text)) FROM history WHERE channel = '"
			  . $Core::channel
			  . "' AND (type = 'public' OR type = 'me')");

		my $summsg;
		my $sumbytes;
		my $match    = shift(@{$res[0]});
		my $summsg   = @{$match}[0];
		my $sumbytes = @{$match}[1];
		@res =
		  $dbh->selectall_arrayref("SELECT author, COUNT(*), SUM(LENGTH(text)) FROM history WHERE channel = '"
			  . $Core::channel
			  . "' AND (type = 'public' OR type = 'me') GROUP BY author ORDER BY COUNT(text) DESC LIMIT 5");
		Core::send_msg("Uzytkownicy "
			  . $Core::channel
			  . " napisali w sumie "
			  . $summsg . " ("
			  . get_length($sumbytes)
			  . ") wiadomosci.");
		Core::send_msg("Top 5 spammers:");
		my $counter = 1;

		foreach $match (@{$res[0]}) {

			Core::send_msg($counter++ . ". "
				. @{$match}[0] . " - "
				. @{$match}[1] . " ("
				. get_length(@{$match}[2])
				. ") messages.");
		}
	}
	else {
		my @res =
		  $dbh->selectall_arrayref("SELECT author, COUNT(*), SUM(LENGTH(text)) FROM history WHERE channel = '"
			  . $Core::channel
			  . "' AND (type = 'public' OR type = 'me') AND author = (?)", undef, $text);
		my $match = shift(@{$res[0]});
		Core::send_msg(@{$match}[0] . " - " . @{$match}[1] . " (" . get_length(@{$match}[2]) . ") messages.");
	}
}


1;
